{
  config,
  lib,
  pkgs,
  ...
}:
let
  inherit (lib) mkOption types;

  cfg = config.programs.astroid;

  jsonFormat = pkgs.formats.json { };

  astroidAccounts = lib.filterAttrs (
    n: v: v.enable && v.astroid.enable
  ) config.accounts.email.accounts;

  boolOpt = b: if b then "true" else "false";

  accountAttr =
    account:
    with account;
    {
      email = address;
      name = realName;
      sendmail = astroid.sendMailCommand;
      additional_sent_tags = "";
      default = boolOpt primary;
      save_drafts_to = "${maildir.absPath}/${folders.drafts}/cur/";
      save_sent = "true";
      save_sent_to = "${maildir.absPath}/${folders.sent}/cur/";
      select_query = "";
    }
    // lib.optionalAttrs (signature.showSignature != "none") {
      signature_attach = boolOpt (signature.showSignature == "attach");
      signature_default_on = boolOpt (signature.showSignature != "none");
      signature_file = pkgs.writeText "signature.txt" signature.text;
      signature_file_markdown = "false";
      signature_separate = "true"; # prepends '--\n' to the signature
    }
    // lib.optionalAttrs (gpg != null) {
      always_gpg_sign = boolOpt gpg.signByDefault;
      gpgkey = gpg.key;
    }
    // astroid.extraConfig;

  # See https://github.com/astroidmail/astroid/wiki/Configuration-Reference
  finalConfig =
    let
      template = lib.importJSON ./astroid-config-template.json;
      astroidConfig = lib.foldl' lib.recursiveUpdate template [
        {
          astroid.notmuch_config = "${config.xdg.configHome}/notmuch/default/config";
          accounts = lib.mapAttrs (n: accountAttr) astroidAccounts;
          crypto.gpg.path = "${pkgs.gnupg}/bin/gpg";
        }
        cfg.extraConfig
        cfg.externalEditor
      ];
    in
    astroidConfig;

in
{
  options = {
    programs.astroid = {
      enable = lib.mkEnableOption "Astroid";

      package = lib.mkPackageOption pkgs "astroid" { nullable = true; };

      pollScript = mkOption {
        type = types.str;
        default = "";
        example = "mbsync gmail";
        description = ''
          Script to run to fetch/update mails.
        '';
      };

      externalEditor = mkOption {
        type = types.nullOr types.str;
        default = null;
        # Converts it into JSON that can be merged into the configuration.
        apply =
          cmd:
          lib.optionalAttrs (cmd != null) {
            editor = {
              "external_editor" = "true";
              "cmd" = cmd;
            };
          };
        example = "nvim-qt -- -c 'set ft=mail' '+set fileencoding=utf-8' '+set ff=unix' '+set enc=utf-8' '+set fo+=w' %1";
        description = ''
          You can use the following variables:

          `%1`
          : file name

          `%2`
          : server name

          `%3`
          : socket ID

          See [Customizing editor](https://github.com/astroidmail/astroid/wiki/Customizing-editor)
          on the Astroid wiki.
        '';
      };

      extraConfig = mkOption {
        type = jsonFormat.type;
        default = { };
        example = lib.literalExpression ''
          {
            poll.interval = 0;
          }
        '';
        description = ''
          JSON config that will override the default Astroid configuration.
        '';
      };
    };

    accounts.email.accounts = mkOption {
      type = with types; attrsOf (submodule (import ./accounts.nix));
    };
  };

  config = lib.mkIf cfg.enable {
    home.packages = lib.mkIf (cfg.package != null) [ cfg.package ];

    xdg.configFile."astroid/config".source = jsonFormat.generate "astroid-config" finalConfig;

    xdg.configFile."astroid/poll.sh" = lib.mkIf (cfg.pollScript != "") {
      executable = true;
      text = ''
        # Generated by Home Manager

        ${cfg.pollScript}
      '';
    };
  };
}
